﻿using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Generic;
using System.Text;
using System.Reflection;

using Developmentalmadness.Web.UI.AjaxAbstractions;

namespace Developmentalmadness.Web.UI.ExtJsControlLibrary
{
    [ParseChildren(true)]
    public abstract class ExtJsContainer : ExtJsBoxComponent, INamingContainer
    {
        private List<ConfigItem> _defaults = new List<ConfigItem>();
        private List<ExtJsComponent> _items = new List<ExtJsComponent>();
        private List<ScriptReference> _references = new List<ScriptReference>();

        private Object _activeItem;

        /// <summary>
        /// Registeres script dependencies with the page
        /// </summary>
        /// <remarks>
        /// This is dependent upon GetScriptDescriptors() being called first.
        /// Otherwise, child dependencies found within the 'items' collection
        /// won't get registered. At the moment this doesn't seem to be a problem
        /// as the .NET Framework (as well as this one) follow this pattern.
        /// 
        /// The reason for this dependency is the 'items' collection. The objects
        /// in the collection aren't instantiated the same way as a control found
        /// in the page or body templates. An object in the collection has no
        /// reference to Page or ScriptManager and it's page events aren't called.
        /// I don't think this is a problem, since the 'items' collection are objects
        /// don't begin their life-cycle until the page is loaded anyway.
        /// </remarks>
        public override List<ScriptReference> GetScriptReferences()
        {
            _references.AddRange(base.GetScriptReferences());

            return _references;
        }

        public override IList<ScriptDescriptor> GetScriptDescriptors()
        {
            IList<ScriptDescriptor> list = base.GetScriptDescriptors();

            ScriptControlDescriptorBase desc = (ScriptControlDescriptorBase)list[0];

            desc.Type = "Ext.Container";

            String[] properties = new String[]{
                "activeItem", "autoDestroy", "bufferResize", "defaultType",
                "hideBorders", "layout", "layoutConfig", "monitorResize"
            };

            Type component = typeof(ExtJsContainer);

            foreach (String property in properties)
            {
                PropertyInfo i = component.GetProperty(property);

                if (i == null)
                    throw new InvalidOperationException(String.Format("Property not found, 'ExtJsContainer.{0}'", property));

                Object value = i.GetValue(this, null);

                if (value == null)
                    continue;

                if (!(value is String))
                    desc.AddPropertyBase(property, value);
                else
                    desc.AddPropertyBase(property, String.Format("'{0}'", value));
            }

            //add defaults
            if (defaults.Count != 0)
            {
                StringBuilder config = new StringBuilder("{");

                for (int i = 0; i < defaults.Count; i++)
                {
                    if (i != 0)
                    {
                        config.Append(",");
#if DEBUG
                        config.AppendLine();
#endif
                    }
                    config.AppendFormat("{0} : '{1}'", defaults[i].name, defaults[i].value);
                }

                config.Append("}");

#if DEBUG
                config.AppendLine();
#endif
                
                desc.AddPropertyBase("defaults", config);
            }

            //add items
            if (items.Count != 0)
            {
                // build items list
                StringBuilder itemList = new StringBuilder("[");

                for (int i = 0; i < items.Count; i++)
                {
                    // add comma between items
                    if (i != 0)
                    {
                        itemList.Append(",");
#if DEBUG
                        itemList.AppendLine("\t\t\t");
#endif
                    }

                    // get the item script
                    IList<ScriptDescriptor> descriptors = items[i].GetScriptDescriptors();
                    
                    // get the items's list of script dependencies
                    List<ScriptReference> references = items[i].GetScriptReferences();

                    // make sure the script dependencies are registered
                    _references.AddRange(references);

                    // write the item's config to the list
                    itemList.Append(((ExtJsScriptControlDescriptor)descriptors[0]).WriteScript(ScriptType.NewOnly));
                }

                itemList.Append("]");

                desc.AddPropertyBase("items", itemList);
            }

            return list;
        }

        /// <summary>
        /// Instantiate the Body template so we can add child controls
        /// </summary>
        protected override void CreateChildControls()
        {
            //call base method
            base.CreateChildControls();

            //verify template exists
            if (Body == null)
                return;

            //instantiate the template in this (Panel)
            Panel container = new Panel();
            Body.InstantiateIn(container);
            container.ID = "Body";
            Controls.Add(container);
        }

        /// <summary>
        /// The content to be added to the Container
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof(ExtJsContainer))]
        [TemplateInstance(TemplateInstance.Single)]
        public ITemplate Body { get; set; }

        /// <summary>
        /// A string component id or the numeric index of the component that should be 
        /// initially activated within the container's layout on render. For example, 
        /// activeItem: 'item-1' or activeItem: 0 (index 0 = the first item in the 
        /// container's collection). activeItem only applies to layout styles that 
        /// can display items one at a time (like Ext.layout.Accordion, 
        /// Ext.layout.CardLayout and Ext.layout.FitLayout). Related to 
        /// Ext.layout.ContainerLayout.activeItem. 
        /// </summary>
        public Object activeItem 
        {
            get
            {
                return _activeItem;
            }
            set
            {
                switch (value.GetType().Name)
                {
                    case "String":
                        Int32 integer;
                        if (Int32.TryParse((String)value, out integer))
                            _activeItem = integer;
                        else if (((String)value).StartsWith("item-"))
                            _activeItem = value;
                        else
                            throw new Exception("activeItem string values must be in the format 'item-1'");
                        break;
                    case "Int16":
                    case "Int32":
                    case "Int64":
                        if (Convert.ToInt64(value) < 0)
                            throw new ArgumentException("Integer values for activeItem must be greater than or equal to 0");

                        _activeItem = value;
                        break;
                    default:
                        throw new ArgumentException("activeItem must be either an integer value or a string in the format 'item-1'");
                }
            }
        }

        public Boolean? autoDestroy { get; set; }

        public Boolean? bufferResize { get; set; }

        public xtype? defaultType { get; set; }

        [NotifyParentProperty(true)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public List<ConfigItem> defaults 
        { 
            get 
            {
                return _defaults;
            } 
        }

        public Boolean? hideBorders { get; set; }

        [NotifyParentProperty(true)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public List<ExtJsComponent> items 
        { 
            get 
            { 
                return _items; 
            } 
        }

        public String layout { get; set; }

        public String layoutConfig { get; set; }

        public Boolean? monitorResize { get; set; }


    }
}
